package processing.core;

/**
 * Android port of the Mobile Processing project - http://mobile.processing.org
 * 
 * The author of Mobile Processing is Francis Li (mail@francisli.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA  02111-1307  USA
 *
 * @author Paul Gregoire (mondain@gmail.com)
 */

import java.io.DataInputStream;
import java.io.InputStream;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
import android.util.Log;

/**
 * 
 */
public class PFont {
	
	private static final String tag = "PFont";
	
    public int charCount;
    
    public PImage images[];
    public char value[];        // char code
    public byte heights[];       // height of the bitmap data
    public byte width[];        // width of bitmap data
    public byte setWidth[];     // width displaced by the char
    public byte topExtent[];    // offset for the top
    public byte leftExtent[];   // offset for the left

    protected int ascii[];        // quick lookup for the ascii chars
    
    public final int baseline;
    public final int height;
    
    public Paint font;
    
    public static Paint defaultTextPaint = new Paint();
    
    //Paint objects to use with LEFT, RIGHT, or CENTER
    private Paint paintLeft;
    private Paint paintRight;
    private Paint paintCenter;    
    
    static {
    	//create a static default "font"
    	defaultTextPaint.setAntiAlias(true);
    	defaultTextPaint.setColor(Color.WHITE);
    	defaultTextPaint.setLinearText(true);
    	defaultTextPaint.setTextSize(12f);
    	defaultTextPaint.setTypeface(Typeface.DEFAULT);
    }
    
    public PFont(Paint font) {
        this.font = font;
        //use bounds rect to determine height
        Rect bounds = new Rect();
        //use the tallest character
        font.getTextBounds("I", 0, 1, bounds);        
        height = bounds.height();
        //ascent returns negative, we want pixel distance
        baseline = Math.abs((int) font.ascent());
        //create left, right, and center versions
    	paintLeft = new Paint(font);
    	paintLeft.setTextAlign(Align.LEFT); 
    	paintRight = new Paint(font);
    	paintRight.setTextAlign(Align.RIGHT); 
    	paintCenter = new Paint(font);
    	paintCenter.setTextAlign(Align.CENTER); 
    }
    
    public PFont(InputStream is, int color, int bgcolor) {
    	//set default paint left in case font is not set
    	paintLeft = new Paint(defaultTextPaint);
    	paintLeft.setTextAlign(Align.LEFT);
        try {
            DataInputStream dis = new DataInputStream(is);

            // number of character images stored in this font
            charCount = dis.readInt();
            // version of format
            int version = dis.readInt();
            Log.d(tag, "Version: " + version);

            // allocate enough space for the character info
            value       = new char[charCount];
            heights     = new byte[charCount];
            width       = new byte[charCount];
            setWidth    = new byte[charCount];
            topExtent   = new byte[charCount];
            leftExtent  = new byte[charCount];

            ascii = new int[128];
            for (int i = 0; i < 128; i++) ascii[i] = -1;

            int baseline = 0;
            int height = 0;            
            // read the information about the individual characters
            for (int i = 0; i < charCount; i++) {
              value[i]      = dis.readChar();
              heights[i]    = dis.readByte();
              width[i]      = dis.readByte();
              setWidth[i]   = dis.readByte();
              topExtent[i]  = dis.readByte();
              leftExtent[i] = dis.readByte();

              // cache locations of the ascii charset
              if (value[i] < 128) ascii[value[i]] = i;
              
              //// get height and baseline
              baseline = Math.max(baseline, topExtent[i]);
              height = Math.max(height, baseline + heights[i] - topExtent[i]);
            }
            this.baseline = baseline;
            this.height = height;

            images = new PImage[charCount];
            int pngSize;
            byte[] pngData;
            byte[] palette = null;
            for (int i = 0; i < charCount; i++) {
                pngSize = dis.readInt();
                if (pngSize > 0) {
                    pngData = new byte[pngSize];
                    dis.readFully(pngData);
                    if ((color != 0) || (bgcolor != 0xffffff)) {
                        //// modify palette before decoding
                        final int offset = 8 /* png signature */ + 25 /* IHDR chunk */ + 8 /* PLTE chunk header */;
                        if (palette == null) {
                            //// calculate a gradient from bgcolor to color
                            int r = (bgcolor & 0xff0000) >> 16;
                            int g = (bgcolor & 0xff00) >> 8;
                            int b = bgcolor & 0xff;
                            int dr = ((color & 0xff0000) >> 16) - r;
                            int dg = ((color & 0xff00) >> 8) - g;
                            int db = (color & 0xff) - b;
                            int index = offset;
                            for (int j = 0; j < 256; j++) {
                                pngData[index++] = (byte) (r + j * dr / 255);
                                pngData[index++] = (byte) (g + j * dg / 255);
                                pngData[index++] = (byte) (b + j * db / 255);
                            }
                            //// recalculate crc
                            int crc = PMIDlet.crc(pngData, offset - 4, (768 + 4));
                            pngData[offset + 768] = (byte) ((crc & 0xff000000) >> 24);
                            pngData[offset + 769] = (byte) ((crc & 0xff0000) >> 16);
                            pngData[offset + 770] = (byte) ((crc & 0xff00) >> 8);
                            pngData[offset + 771] = (byte) (crc & 0xff);
                            //// save new palette
                            palette = new byte[768 + 4];
                            System.arraycopy(pngData, offset, palette, 0, 768 + 4);
                        } else {
                            System.arraycopy(palette, 0, pngData, offset, 768 + 4);
                        }
                    }
                    images[i] = new PImage(pngData);
                }
            }            
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
    
    public int getIndex(char c) {
        if (c < 128) {
            return ascii[c];
        }
        return getIndex(c, 0, charCount - 1);
    }

    private int getIndex(int c, int start, int stop) {
        int pivot = (start + stop) / 2;
        // if this is the char, then return it
        if (c == value[pivot]) return pivot;
        // char doesn't exist, otherwise would have been the pivot
        if (start >= stop) return -1;
        // if it's in the lower half, continue searching that
        if (c < value[pivot]) return getIndex(c, start, pivot-1);
        // if it's in the upper half, continue there
        return getIndex(c, pivot+1, stop);
    }
    
    public int charsWidth(char[] ch, int offset, int length) {
        int result = 0;
        if (font != null) {
        	result = (int) font.measureText(ch, offset, length);
        } else {
            int index;
            for (int i = offset, end = offset + length; i < end; i++) {
                index = getIndex(ch[i]);
                if (index >= 0) {
                    result += setWidth[index];
                } else {
                    result += setWidth[ascii['i']];
                }                
            }
        }
        return result;
    }
    
    public int charWidth(char ch) {
        int result;
        if (font != null) {
        	char[] chars = new char[]{ch};
        	result = (int) font.measureText(String.valueOf(chars));
        } else {
            int index = getIndex(ch);
            if (index >= 0) {
                result = setWidth[index];
            } else {
                //// for unknown characters give back some default value (this case, width of letter 'i')
                result = setWidth[ascii['i']];
            }
        }
        return result;
    }
    
    public int stringWidth(String str) {
        int result;
        if (font != null) {
        	result = (int) font.measureText(str);
        } else {
            result = substringWidth(str, 0, str.length());
        }
        return result;
    }
    
    public int substringWidth(String str, int offset, int length) {
        int result = 0;
        if (font != null) {
        	result = (int) font.measureText(str, offset, (offset + length));
        } else {
            int index;
            for (int i = offset, end = offset + length; i < end; i++) {
                index = getIndex(str.charAt(i));
                if (index >= 0) {
                    result += setWidth[index];
                } else {
                    result += setWidth[ascii['i']];
                }                
            }            
        }
        return result;
    }

    public void draw(Canvas g, String str, int x, int y, Paint color) {
    	g.drawText(str, x, y, color);
    }
    
    public void draw(Canvas g, String str, int x, int y, int textAlign) {
        if (font != null) {
            Paint align = paintLeft;
            if (textAlign == PMIDlet.CENTER) {
                align = paintCenter;
            } else if (textAlign == PMIDlet.RIGHT) {
                align = paintRight;
            }
            //set our paint
        	g.drawPaint(align);
        	//create bounds to determine height
            Rect bounds = new Rect();
            font.getTextBounds(str, 0, str.length(), bounds);            
            g.drawText(str, (float) x, (float) y - bounds.height(), align);
        } else {
            if (textAlign != PMIDlet.LEFT) {
                int width = stringWidth(str);
                if (textAlign == PMIDlet.CENTER) {
                    x -= width >> 1;
                } else if (textAlign == PMIDlet.RIGHT) {
                    x -= width;
                }
            }
            char c;
            int index;
            for (int i = 0, length = str.length(); i < length; i++) {
                c = str.charAt(i);
                index = getIndex(c);
                if (index >= 0) {
                    g.drawBitmap(images[index].image, (float) x + leftExtent[index], (float) y - topExtent[index], paintLeft);
                    x += setWidth[index];
                } else {
                    x += setWidth[ascii['i']];
                }
            }
        }
    }
}
